-- | The full generic Style file for the linear algebra domain
-- | Author: Dor Ma'ayan
--   August, 2018

-- Global constants and sizes
const {
  const.vectorSpaceSize = 400.0
  const.axisSize = const.vectorSpaceSize * 0.45
  const.distVectorSpaces = 50.0
  const.scaleRatio = 200.0
  const.fontSize = "14pt"
  const.lightenFrac = 0.5
  const.arrowThickness = 3.0
  const.arrowThickness2 = 2.0
  const.arrowheadSize = 0.5
  const.repelWeight = 0.7
}

-- Global RGB colors in common use
Colors {
    Colors.black = rgba(0.0, 0.0, 0.0, 1.0)
    Colors.lightBlue = rgba(0.1, 0.1, 0.9, 0.1)
    Colors.darkBlue = rgba(0.05, 0.05, 0.6, 0.5)
    Colors.lightGray = rgba(200.0,200.0,200.0,1.0)
    Colors.darkGray = rgba(0.4, 0.4, 0.4, 1.0)

    Colors.gray = rgba(0.8, 0.8, 0.8, 1.0)
    Colors.red = rgba(1.0, 0.0, 0.0, 1.0)
    Colors.pink = rgba(1.0, 0.4, 0.7, 1.0)
    Colors.yellow = rgba(1.0, 1.0, 0.0, 1.0)
    Colors.orange = rgba(1.0, 0.6, 0.0, 1.0)
    Colors.lightorange = rgba(1.0, 0.6, 0.0, 0.25)
    Colors.green = rgba(0.0, 0.8, 0.0, 1.0)
    Colors.blue = rgba(0.0, 0.0, 1.0, 1.0)
    Colors.sky = rgba(0.325, 0.718, 0.769, 1.0)
    Colors.lightsky = rgba(0.325, 0.718, 0.769, 0.25)
    Colors.lightblue = rgba(0.0, 0.0, 1.0, 0.25)
    Colors.cyan = rgba(0.0, 1.0, 1.0, 1.0)
    Colors.purple = rgba(0.5, 0.0, 0.5, 1.0)
    Colors.white = rgba(1.0, 1.0, 1.0, 1.0)
    Colors.none = rgba(0.0, 0.0, 0.0, 0.0)
    Colors.bluegreen = rgba(0.44, 0.68, 0.60, 1.0)
}

VectorSpace U {
    U.thickness = 1.5
    U.axisColor = Colors.darkGray

    U.shape = Square {
	x : 0.0
	y : 0.0
        side : const.vectorSpaceSize
        color : Colors.lightBlue
    }

    U.left = U.shape.x + (U.shape.side/2.0)
    U.right = U.shape.x - (U.shape.side/2.0)

    -- U.textX = Text {
    --     string : "x"
    --     color : Colors.darkGray
    --     fontSize : const.fontSize
    -- }

    -- U.textY = Text {
    --     string : "y"
    --     color : Colors.darkGray
    --     fontSize : const.fontSize
    -- }

    /*
    U.perpMark = Arc {
        x : U.shape.x
        y : U.shape.y
        size : 10
        rotation : 0
        isRight : "true"
        color : Colors.black
    }
    */

    U.xAxis = Line {
        startX : U.shape.x - const.axisSize
        startY : U.shape.y
        endX : U.shape.x + const.axisSize
        endY : U.shape.y
        thickness : U.thickness
        color : U.axisColor
    }

    U.yAxis = Line {
        startX : U.shape.x
        startY : U.shape.y - const.axisSize
        endX : U.shape.x
        endY : U.shape.y + const.axisSize
        thickness : U.thickness
        color : U.axisColor
    }

    U.text = Text {
        string : U.label
        x : U.shape.x - const.axisSize
        y : U.shape.y + const.axisSize
        color : U.axisColor
        fontSize : const.fontSize
    }
}

-- TODO: generates too many constraints?

VectorSpace U; VectorSpace V {
    --LOCAL.sameHeight = ensure sameHeight(U.shape, V.shape)
    --LOCAL.nonIntersect = ensure disjoint(U.shape, V.shape)
    --V.shape.y = 0.0
    --U.shape.x = 0.0
    --V.shape.x = 0.0
}

Vector v
with VectorSpace U
where In(v,U) {
  v.text = Text {
    string : v.label
    color : v.shape.color
    fontSize : const.fontSize
  }

  v.color = sampleColor(1.0) -- Full opacity to hide arrowhead layering

  v.pad1 = ?
  v.pad2 = ?

  v.shape = Arrow {
    startX : U.shape.x
    startY : U.shape.y
    -- endX : ?
    -- endY : ?
    -- So the labels can be optimized separately
    endX : sampleNum(U.left, U.right) + v.pad1
    endY : sampleNum(U.left, U.right) + v.pad2

    thickness : const.arrowThickness
    color : v.color
    arrowheadSize : const.arrowheadSize
  }

  -- v.shape1 = Line {
  --   startX : U.shape.x
  --   startY : U.shape.y
  --   endX : v.shape.endX
  --   endY : v.shape.endY
  --   thickness : 5.0
  --   color : setOpacity(v.color, 0.5)
  -- }

   v.vector = (v.shape.endX - v.shape.startX, v.shape.endY - v.shape.startY)
   v.angle = angle(v.vector)

  v.layeringText1 = v.text above U.xAxis
  v.layeringText2 = v.text above U.yAxis

   v.containFn = ensure contains(U.shape, v.shape)
   v.containLabel = ensure contains(U.shape, v.text)
   -- NOTE: nearHead is actually an objective. Use the other constraint form?
   v.labelLocation = ensure nearHead(v.shape, v.text, 0.0, 30.0)
   v.labelAvoidFn = encourage repel(v.shape, v.text, const.repelWeight)

   -- TODO: repel only if it overlaps? (Do a bbox check before doing the expensive sample + sum operation)
   v.labelAvoidAxisFn1 = encourage repel(U.xAxis, v.text, const.repelWeight)
   v.labelAvoidAxisFn2 = encourage repel(U.yAxis, v.text, const.repelWeight)
   v.labelAvoidAxisText = encourage repel(U.text, v.text, const.repelWeight)
}

Vector u
with Vector v; VectorSpace U
where u := neg(v); In(v,U); In(u, U) {
  override u.shape.endX = 2.0 * v.shape.startX - v.shape.endX
  override u.shape.endY = 2.0 * v.shape.startY - v.shape.endY
}

Vector u
with Vector v; Vector w; VectorSpace U
where u := addV(v,w); In(u, U); In(v, U); In(w, U) {
   override u.shape.endX = v.shape.endX + w.shape.endX - U.shape.x
   override u.shape.endY = v.shape.endY + w.shape.endY - U.shape.y
   override u.shape.color = blendColor(v.shape.color, w.shape.color)

   -- TODO: Arrow shape has a length slightly longer than the line's ending position
   -- I'm not sure how to scale the arrow to deal with arrowhead size either

   -- TODO: fix this opacity too -- lighter color
    u.slider_v = Arrow {
        endX : u.shape.endX
        endY : u.shape.endY

        startX : w.shape.endX
        startY : w.shape.endY
        thickness : const.arrowThickness2
        color : scaleColor(v.color, const.lightenFrac)
        style : "dashed"
        arrowheadSize : const.arrowheadSize
    }

    u.slider_w = Arrow {
       endX : u.shape.endX
       endY : u.shape.endY

       startX : v.shape.endX
       startY : v.shape.endY
       thickness : const.arrowThickness2
        color : scaleColor(w.color, const.lightenFrac)
       style : "dashed"
       arrowheadSize : const.arrowheadSize
    }

    u.sw_layering = u.slider_w below u.shape
    u.sv_layering = u.slider_v below u.shape
}

LinearMap f
with VectorSpace U; VectorSpace V
where From(f,U,V) {
   /*f.shape = Arrow {
      startX : U.shape.x
      startY : U.shape.y + const.vectorSpaceSize/2.0
      endX : V.shape.x
      endY : V.shape.y + const.vectorSpaceSize/2.0
      style : "curved"
      rotation : 0.0
   }*/

   V.shape.x = U.shape.x + const.vectorSpaceSize 
   V.shape.y = U.shape.y

   f.shape = Curve {
     pathData : makeCurve(0.65*U.shape.x+0.35*V.shape.x,0.65*U.shape.y+0.35*V.shape.y,0.35*U.shape.x+0.65*V.shape.x,0.35*U.shape.y+0.65*V.shape.y, 0.0, 30.0)
     fill : Colors.none
     strokeWidth : 1.5
     rightArrowhead : True
   }

   f.text = Text {
     string : f.label
     x : (U.shape.x + V.shape.x) / 2.0
     y : U.shape.y + 50.0
     color : f.shape.color
     fontSize : const.fontSize
   }

   -- define an actual linear map f(x,y) = ax + by
   -- TODO should pick this at random, rather than using a fixed map...
   f.c0 = (-1.0,0.0)
   f.c1 = (0.0,0.5)

   f.orderFn = ensure lessThan(U.shape.x + 20.0, V.shape.x)
}

-- | Sclar is not visualized in any concrete way but insteat it contains a
--   Real value
Scalar c {
  c.val = generateRandomReal()
}

Scalar c
with Vector a; Vector b; VectorSpace U
where c := determinant(a,b); In(a,U); In(b,U) {
   c.shape = Curve {
   	   pathData : makeRegionPath(a.shape, b.shape)
	   strokeWidth : 0.0
	   fill : setOpacity(Colors.sky, 0.25)
   }

/* c.shape = Parallelogram {
      x : U.shape.x
      y : U.shape.y
      w : len(a.shape)
      h : len(b.shape)
      angle : calcVectorsAngle(a.shape.startX,a.shape.startY,a.shape.endX,a.shape.endY, b.shape.startX,b.shape.startY,b.shape.endX,b.shape.endY)
      rotation : calcVectorsAngleWithOrigin(a.shape.startX,a.shape.startY,a.shape.endX,a.shape.endY, b.shape.startX,b.shape.startY,b.shape.endX,b.shape.endY)
      stroke-style : "dashed"
   } */

   c.text = Text {
     string : c.label
     -- Normalize for origin
     x : (a.shape.endX + b.shape.endX - a.shape.startX) / 2.0
     y : (a.shape.endY + b.shape.endY - a.shape.startY) / 2.0
     color : Colors.sky
     fontSize : const.fontSize
   }

   c.layering = c.text above c.shape
}

Scalar c
with Vector a; Vector b; VectorSpace U
where c := innerProduct(a,b); In(a,U); In(b,U) {
    override c.val = len(a.shape) * calcVectorsAngleCos(a.shape.startX,a.shape.startY,a.shape.endX,a.shape.endY, b.shape.startX,b.shape.startY,b.shape.endX,b.shape.endY) / 200.0
    c.line1 = Line {
        startX : b.shape.startX
        startY : b.shape.startY
        endX :  b.shape.endX * c.val - b.shape.startX * (c.val - 1.0)
        endY : b.shape.endY * c.val - b.shape.startY * (c.val - 1.0)
        thickness : 2.5
        style : "dashed"
    }

    c.line2 = Line {
        startX : a.shape.endX
        startY : a.shape.endY
        endX : b.shape.endX * c.val - b.shape.startX * (c.val - 1.0)
        endY : b.shape.endY * c.val - b.shape.startY * (c.val - 1.0)
        thickness : 2.5
        style : "dashed"
    }

    /*
        c.line1 =
        u.shape.endX = v.shape.endX * c.val - v.shape.startX * (c.val - 1.0)
        u.shape.endY = v.shape.endY * c.val - v.shape.startY * (c.val - 1.0)
        c.line1 =
        */
}

Scalar c
with Vector a;  VectorSpace U
where c := norm(a); In(a,U) {
   c.norm = len(a.shape)
   override c.val = c.norm / const.scaleRatio --calcnorm(a.shape.startX,a.shape.startY,a.shape.endX,a.shape.endY)

   c.padding = 20.0

   -- c.shape = Brace {
   c.shape = Line {
     startX : a.shape.startX
     startY : a.shape.startY + c.padding
     endX : a.shape.endX
     endY : a.shape.endY + c.padding
     thickness : 1.5
     color : setOpacity(a.shape.color, 0.75)
   }

   c.text = Text {
        string : c.label
        x : c.padding + midpointX(c.shape) 
        y : c.padding + midpointY(c.shape) 
        color : c.shape.color
        fontSize : const.fontSize
   }
}

Vector u
with Scalar c; VectorSpace U; Vector v
where u := scale(c,v); In(u,U); In(v,U){
    u.shape.endX = v.shape.endX * c.val - v.shape.startX * (c.val - 1.0)
    u.shape.endY = v.shape.endY * c.val - v.shape.startY * (c.val - 1.0)

    -- u.shape.color = setOpacity(v.shape.color, 0.75)

    c.layeringScale = u.shape above v.shape
}

/*
Vector u, Vector v
where Orthogonal( u, v ) {
   local.angle = angle( u.vector ) 
   local.shape = Square {
       center : (0,0)
       size : .5
       stroke-color : Black 
       fill-color : White
       transform : Rotate(local.angle) then Translate(u.shape.start)
   }

   ensure Equals( Dot( u.vector, v.vector ), 0 ) }
*/

Vector u; Vector v
with VectorSpace U
where Orthogonal(u, v); In(u, U); In(v, U) {
      -- TODO: fix this angle calculation so it works when both vectors are in quadrants 2 or 3
      LOCAL.angle = min(u.angle, v.angle)
      LOCAL.len = 20.0

      -- Scale by side len. Then translate so default square's corner is at (0,0).
      -- Then rotate by the vector angle. Then translate to the local origin of the vector space.
      LOCAL.shape = SquareTransform {
	  -- TODO: unintuitive behavior; need to set these constants otherwise shape is offscreen
	  x : 0.0
	  y : 0.0
	  side : 1.0
	  rotation : 0.0

	  transform : andThen(andThen(andThen(scale(LOCAL.len, LOCAL.len), translate(LOCAL.len / 2.0, LOCAL.len / 2.0)), rotate(LOCAL.angle)), translate(U.shape.x, U.shape.y))
	  color : Colors.none
	  strokeColor : Colors.black
	  strokeWidth : 0.1
      }

      -- LOCAL.perpFn = ensure equals(dot(u.vector, v.vector), 0.0)
      LOCAL.perpFn = encourage equal(dot(u.vector, v.vector), 0.0)

      LOCAL.layering1 = v.shape above LOCAL.shape
      LOCAL.layering2 = u.shape above LOCAL.shape
}

-- No label overlaps with any other label, vector, slider vector, or axis line. (This doesn't match the same vector twice, right?)
Vector u; Vector v {
       -- NOTE: This matches (u,v) and (v,u), generating double objectives

       -- TODO: this doesn't quite work b/c it doesn't account for the thickness of the label

       -- TODO: this currently makes diagrams too symmetrical. Maybe do a second labeling pass?
       LOCAL.labelAvoidSegFn = encourage repel(v.shape, u.text, const.repelWeight)
      LOCAL.labelAvoidTextFn = encourage repel(v.text, u.text, const.repelWeight)
}
